﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using System.IO;

namespace Assets.Scripts.LDS
{
    //finished
    class SemNode
    {

        public enum NodeType
        {
            Obj = 0,
            Attri,
            Pair,
            Group,
            Command
        };


        public SemNode(string t, string n, int id)
        {
            nodeType = t; nodeName = n; nodeId = id;
            isAligned = false; isSynthesized = false; matchingStatus = 0;
            isInferred = false; inferedType = -1; inferRefNodeId = -1; isAnnotated = false; useNewInst = false;
        }

        public SemNode(SemNode sn)
        {
            nodeType = sn.nodeType; nodeName = sn.nodeName; nodeId = sn.nodeId;
            isAligned = sn.isAligned; isSynthesized = sn.isSynthesized; useNewInst = sn.useNewInst;
            isInferred = sn.isInferred; inferedType = sn.inferedType; inferRefNodeId = sn.inferRefNodeId;
            matchingStatus = sn.matchingStatus;
            isAnnotated = sn.isAnnotated;


            inEdgeNodeList = new List<int>(sn.inEdgeNodeList);

            outEdgeNodeList = new List<int>(sn.outEdgeNodeList);

            nodeLabels = new List<int>(sn.nodeLabels);  // idx of per-object attribute node, valid for "object" node
            activeNodeList = new List<int>(sn.activeNodeList); // idx of active object node, valid for "relation" node
            anchorNodeList = new List<int>(sn.anchorNodeList);

            inPlace = sn.inPlace;
        }


        // node types: object, p_attribute, p_relation, g_relation, g_attribute, 
        public string nodeType;

        // model category name, relationship name or attribute name, e.g. chair, support, messy
        public string nodeName;

        public int nodeId;


        public bool isAligned;
        public bool isSynthesized;
        public bool useNewInst; // whether use a new instance for current object node

        public enum InferNodeType
        {
            InferBySupport = 0,
            InferByContext
        };

        public bool isInferred;
        public int inferedType;
        public int inferRefNodeId;  // reference object id of the inferred object

        public enum NodeMatchingStatus
        {
            ContextNode = 1,
            ExplicitNode
        };

        public int matchingStatus;

        public bool isAnnotated;


        // node id of the other end of in edge
        // for object node: saves the non-object node (relations, attributes) id to the object node, e.g. table <-- dining,  chair <-- support
        // for relation/attribute node: saves passive object id to relation/attribute node, e.g. support <-- table
        public List<int> inEdgeNodeList = new List<int>();

        // node id of the other end of out edge
        // for relation/attribute node: from relation/attribute node to object, e.g. messy --> book, messy --> utensils
        // for object: from passive object to relation/attribute node, e.g. table --> support
        public List<int> outEdgeNodeList = new List<int>();

        // parsed from in and out edge node list
        public List<int> nodeLabels = new List<int>();  // idx of per-object attribute node, valid for "object" node
        public List<int> activeNodeList = new List<int>(); // idx of active object node, valid for "relation" node
        public List<int> anchorNodeList = new List<int>(); // idx of anchor object node, valid for "relation" node

        // use to layout optimize. only for pair_relation node.
        public bool inPlace = false;
    }


    class SemEdge
    {
        public SemEdge(int s, int t, int id) { sourceNodeId = s; targetNodeId = t; edgeId = id; }

        public SemEdge(SemEdge se)
        {
            sourceNodeId = se.sourceNodeId;
            targetNodeId = se.targetNodeId;
            edgeId = se.edgeId;
        }

        public void reverseEdgeDir()
        {
            int tempId = sourceNodeId;
            sourceNodeId = targetNodeId;
            targetNodeId = tempId;
        }

        public int sourceNodeId, targetNodeId;
        public int edgeId;
    };

    /// <summary>
    /// SemanticGraph
    /// </summary>
    /// alignRelationNodesToGraph and alignObjectNodesToGraph are align fucntion
    class SemanticGraph
    {
        public int m_nodeNum, m_edgeNum;
        public List<SemNode> m_nodes = new List<SemNode>();
        public List<SemEdge> m_edges = new List<SemEdge>();

        // improve: canditate
        public Dictionary<int, List<int>> m_canditate_AlignNode = new Dictionary<int, List<int>>();

        public Dictionary<int, int> m_nodeAlignMap = new Dictionary<int, int>();  // current to target node id alignment map
        List<string> m_strictAttriSet = new List<string>(); // strict attribute that must be satisfied
        List<string> m_groupAttriSet = new List<string>();

        public bool m_isCommand;

        public static readonly string[] SSGNodeType = { "object", "attribute", "pair_relation", "group_relation", "command" };

        public SemanticGraph()
        {
            m_nodeNum = 0;
            m_edgeNum = 0;
            m_isCommand = false;
        }
        public SemanticGraph(SemanticGraph sg)
        {

            // generate a copy of sg
            m_nodeNum = sg.m_nodeNum;
            m_edgeNum = sg.m_edgeNum;

            //m_nodes = sg.m_nodes;
            m_nodes = Util.CopyList<SemNode>(sg.m_nodes);
            m_edges = Util.CopyList<SemEdge>(sg.m_edges);

            m_nodeAlignMap = new Dictionary<int, int>(sg.m_nodeAlignMap);

            m_isCommand = sg.m_isCommand;
            initAttributeSet();
        }


        public void addNode(string nType, string nName)
        {
            string unifiedType = nType;

            if (nType == "group_relation_anno")
            {
                unifiedType = SSGNodeType[(int)SemNode.NodeType.Group];
            }

            SemNode newNode = new SemNode(unifiedType, nName, m_nodeNum);
            m_nodes.Add(newNode);

            m_nodeNum++;
        }

        public void addNode(SemNode node)
        {
            // The edge information is stored and updated when addedge
            SemNode newNode = new SemNode(node.nodeType, node.nodeName, m_nodeNum);
            newNode.inPlace = node.inPlace;
            newNode.isAligned = node.isAligned;
            newNode.matchingStatus = node.matchingStatus;
            newNode.isSynthesized = node.isSynthesized;
            newNode.useNewInst = node.useNewInst;

            m_nodes.Add(newNode);
            m_nodeNum++;
        }


        public void addEdge(int s, int t) // after adding edge, parseNodeNeighbors must be called to update the neighbor list
        {
            SemEdge newEdge = new SemEdge(s, t, m_edgeNum);

            m_edges.Add(newEdge);

            // update node edge list
            m_nodes[s].outEdgeNodeList.Add(t);
            m_nodes[t].inEdgeNodeList.Add(s);

            m_edgeNum++;
        }
        public void parseNodeNeighbors()  // parse in and out node list to node labels, active and anchor node list
        {
            for (int i = 0; i < m_nodeNum; i++)
            {
                SemNode sgNode = m_nodes[i];

                if (sgNode.nodeType == "object")
                {
                    sgNode.nodeLabels.Clear();

                    for (int ai = 0; ai < sgNode.inEdgeNodeList.Count; ai++)
                    {
                        int attNodeId = sgNode.inEdgeNodeList[ai];  // id of attribute node in tsg
                        SemNode attNode = this.m_nodes[attNodeId];

                        if (attNode.nodeType == "attribute")
                        {
                            sgNode.nodeLabels.Add(attNodeId);
                        }
                    }
                }

                if (sgNode.nodeType.Contains("relation") || sgNode.nodeType.Contains("attribute"))
                {
                    sgNode.activeNodeList.Clear();
                    sgNode.anchorNodeList.Clear();

                    // edge dir: (active, relation), (relation, reference)

                    for (int ai = 0; ai < sgNode.inEdgeNodeList.Count; ai++)
                    {
                        int inNodeId = sgNode.inEdgeNodeList[ai];
                        sgNode.activeNodeList.Add(inNodeId);
                    }

                    for (int ai = 0; ai < sgNode.outEdgeNodeList.Count; ai++)
                    {
                        int outNodeId = sgNode.outEdgeNodeList[ai];
                        sgNode.anchorNodeList.Add(outNodeId);
                    }
                }
            }
        }


        void initAttributeSet()
        {
            m_strictAttriSet.Add("sofa");
            m_strictAttriSet.Add("round");
            m_strictAttriSet.Add("rectangular");
            m_strictAttriSet.Add("queen");
            m_strictAttriSet.Add("coffee");
            m_strictAttriSet.Add("dining");
            m_strictAttriSet.Add("sauce");

            m_groupAttriSet.Add("messy");
            m_groupAttriSet.Add("organized");
            m_groupAttriSet.Add("disorganized");
            m_groupAttriSet.Add("formal");
            m_groupAttriSet.Add("casual");
            m_groupAttriSet.Add("clean");
            m_groupAttriSet.Add("work");
            m_groupAttriSet.Add("study");
        }

        bool isStrictAttribute(string attriName)
        {
            return m_strictAttriSet.Contains(attriName);

        }

        bool isGroupAttribute(string attriName)
        {
            return m_groupAttriSet.Contains(attriName);
        }



        // wait
        public List<string> getAttriNamesForNode(int nodeId)
        {
            List<string> currAttriNames = new List<string>();
            SemNode currNode = m_nodes[nodeId];

            for (int t = 0; t < currNode.nodeLabels.Count; t++)
            {
                int attNodeId = currNode.nodeLabels[t];
                SemNode attNode = m_nodes[attNodeId];
                currAttriNames.Add(attNode.nodeName);
            }

            return currAttriNames;
        }

        public List<int> findNodeWithName(string nName)
        {
            List<int> nodeIds = new List<int>();

            for (int i = 0; i < m_nodeNum; i++)
            {
                if (m_nodes[i].nodeName == nName)
                {
                    nodeIds.Add(i);
                }
            }

            return nodeIds;
        }

        public bool isEdgeExist(int s, int t)
        {
            for (int i = 0; i < m_edgeNum; i++)
            {
                if (m_edges[i].sourceNodeId == s && m_edges[i].targetNodeId == t)
                {
                    return true;
                }
            }

            return false;
        }

        protected SemEdge getEdge(int s, int t)
        {
            for (int i = 0; i < m_edgeNum; i++)
            {
                if (m_edges[i].sourceNodeId == s && m_edges[i].targetNodeId == t)
                {
                    return m_edges[i];
                }
            }
            TextDialog.textDebug(String.Format("getEdge({0} ,{1}) return NULL", s, t));
            return null;
        }


        private double MapNode(int selNodeID, int targetNodeID, SemanticGraph targetGraph)
        {
            double alignScore = 0;

            List<double> NodeScore = new List<double> { 0, 0, 10 };

            SemNode objNode = this.m_nodes[selNodeID], tarObjNode = targetGraph.m_nodes[targetNodeID];

            objNode.isAligned = true;
            tarObjNode.isAligned = true;

            objNode.inPlace = true;
            tarObjNode.inPlace = true;


            tarObjNode.matchingStatus = objNode.matchingStatus;
            tarObjNode.useNewInst = objNode.useNewInst;

            m_nodeAlignMap[selNodeID] = targetNodeID; // save aligned object node into map									
            alignScore += NodeScore[objNode.matchingStatus];

            if (objNode.nodeType == "object")
            {
                // add the attributes node
                if (objNode.nodeLabels.Count > 0)
                {
                    for (int ai = 0; ai < objNode.nodeLabels.Count; ai++)
                    {
                        int attNodeId = objNode.nodeLabels[ai];

                        SemNode attNode = this.m_nodes[attNodeId];
                        if (tarObjNode.nodeLabels.Count != 0)
                        {
                            for (int tarAi = 0; tarAi < tarObjNode.nodeLabels.Count; tarAi++)
                            {
                                int tarAttNodeId = tarObjNode.nodeLabels[tarAi]; // id of attribute node in dbssg
                                SemNode tarAttNode = targetGraph.m_nodes[tarAttNodeId];

                                if (attNode.nodeType == tarAttNode.nodeType && attNode.nodeName == tarAttNode.nodeName)
                                {
                                    attNode.isAligned = true;
                                    tarAttNode.isAligned = true;
                                    tarAttNode.matchingStatus = attNode.matchingStatus;

                                    m_nodeAlignMap[attNodeId] = tarAttNodeId; // save aligned attribute node into map

                                    if (isStrictAttribute(attNode.nodeName))
                                        alignScore += 5;  // 
                                }
                            }
                        }
                    }
                }
                else
                {
                    if (tarObjNode.nodeLabels.Count != 0)
                    {
                        for (int tarAi = 0; tarAi < tarObjNode.nodeLabels.Count; tarAi++)
                        {
                            int tarAttNodeId = tarObjNode.nodeLabels[tarAi]; // id of attribute node in dbssg
                            SemNode tarAttNode = targetGraph.m_nodes[tarAttNodeId];
                            if (isStrictAttribute(tarAttNode.nodeName))
                                alignScore -= 5;  // attribute node does not contribute to matching score
                        }
                    }
                }

            }

            return alignScore;
        }

        public void alignObjectNodesToGraph(SemanticGraph targetGraph, ref double alignScore, bool userDeterminer = false)
        {
            double[] NodeScore = { 0, 0, 10 };

            // improve
            m_canditate_AlignNode.Clear();

            // first match object node and per-object attribute node
            for (int qNi = 0; qNi < this.m_nodeNum; qNi++)
            {
                SemNode objNode = this.m_nodes[qNi];

                if (objNode.nodeType == "object")
                {

                    // not aligned this node if marked for use new instance
                    if (userDeterminer && objNode.useNewInst) continue;

                    for (int tarNi = 0; tarNi < targetGraph.m_nodeNum; tarNi++)
                    {
                        SemNode tarObjNode = targetGraph.m_nodes[tarNi];
                        // skip the aligned nodes
                        if (tarObjNode.nodeType == "object" && objNode.nodeName == tarObjNode.nodeName)
                        {
                            // if node has attributes
                            if (objNode.nodeLabels.Count != 0)
                            {
                                int matchedAttNum = 0;
                                int currSgAttNum = 0;

                                // count num of attribute node; 
                                for (int ai = 0; ai < objNode.nodeLabels.Count; ai++)
                                {
                                    int attNodeId = objNode.nodeLabels[ai];

                                    SemNode attNode = this.m_nodes[attNodeId];

                                    // only strict attribute counts
                                    if (isStrictAttribute(attNode.nodeName))
                                    {
                                        currSgAttNum++;
                                    }
                                    else
                                    {
                                        continue;
                                    }

                                    if (tarObjNode.nodeLabels.Count != 0)
                                    {
                                        for (int tarAi = 0; tarAi < tarObjNode.nodeLabels.Count; tarAi++)
                                        {
                                            int tarAttNodeId = tarObjNode.nodeLabels[tarAi]; // id of attribute node in dbssg
                                            SemNode tarAttNode = targetGraph.m_nodes[tarAttNodeId];

                                            // 1.isAligned -> addList
                                            if (attNode.nodeType == tarAttNode.nodeType && attNode.nodeName == tarAttNode.nodeName)
                                            {
                                                if (!isGroupAttribute(tarAttNode.nodeName))
                                                {
                                                    matchedAttNum++;
                                                }
                                                else if (tarAttNode.inEdgeNodeList.Count > 8)  // only matched to the large group
                                                {
                                                    matchedAttNum++;
                                                }
                                            }
                                        }
                                    }
                                }

                                //  if all attribute nodes matched, then the node is matched
                                if (matchedAttNum == currSgAttNum)
                                {
                                    if (currSgAttNum == 2)
                                    {
                                        TextDialog.textDebug("currSgAttNum == 2");
                                    }

                                    // improve.
                                    if (!m_canditate_AlignNode.ContainsKey(qNi)) m_canditate_AlignNode[qNi] = new List<int>();
                                    m_canditate_AlignNode[qNi].Add(tarNi);

                                    //break;
                                }
                            }
                            // if node does not has attributes
                            else
                            {
                                //objNode.isAligned = true;
                                //tarObjNode.isAligned = true;
                                //tarObjNode.matchingStatus = objNode.matchingStatus;
                                //tarObjNode.useNewInst = objNode.useNewInst;

                                // imporve
                                if (!m_canditate_AlignNode.ContainsKey(qNi)) m_canditate_AlignNode[qNi] = new List<int>();
                                m_canditate_AlignNode[qNi].Add(tarNi);

                                //m_nodeAlignMap[qNi] = tarNi; // save aligned object node map
                                //alignScore += NodeScore[objNode.matchingStatus];

                                // give some penalty if query node has no attributes, but matched node has attributes

                                //break;
                            }
                        }
                    }
                }
            }
        }


        // now we use the pair relation to make it certain which node is the mapnode in canditate.
        public void alignRelationNodesToGraph(SemanticGraph targetGraph, ref double alignScore)
        {
            // pair_relation
            for (int qNi = 0; qNi < m_nodeNum; qNi++)
            {
                SemNode relNode = m_nodes[qNi];

                // align pair-wise relationship node
                if (relNode.nodeType == "pair_relation")
                {
                    // To test whether in and out node exist
                    if (relNode.activeNodeList.Count == 0 || relNode.anchorNodeList.Count == 0) continue;

                    // edge dir: (active, relation), (relation, reference)
                    int actNodeId = relNode.activeNodeList[0];
                    int anchorNodeId = relNode.anchorNodeList[0];

                    // if any object node is not in the matched map (not matched), then break
                    if (!m_canditate_AlignNode.ContainsKey(actNodeId) || !m_canditate_AlignNode.ContainsKey(anchorNodeId)) continue;

                    for (int tarNi = 0; tarNi < targetGraph.m_nodeNum; tarNi++)
                    {
                        SemNode tarRelNode = targetGraph.m_nodes[tarNi];
                        // skip the aligned nodes
                        if (!tarRelNode.isAligned && tarRelNode.nodeType == "pair_relation" && relNode.nodeName == tarRelNode.nodeName)
                        {
                            if (m_canditate_AlignNode[actNodeId].Contains(tarRelNode.activeNodeList[0])
                                && m_canditate_AlignNode[anchorNodeId].Contains(tarRelNode.anchorNodeList[0]))
                            {
                                // if already aligned and not aligned this, then gonon
                                //if (m_nodes[actNodeId].isAligned && m_nodeAlignMap[actNodeId] != tarRelNode.activeNodeList[0]) continue;
                                if (m_nodes[anchorNodeId].isAligned && m_nodeAlignMap[anchorNodeId] != tarRelNode.anchorNodeList[0]) continue;

                                // if a relation is aligned, the node has same relation is aligned

                                int mapActNodeId = tarRelNode.activeNodeList[0];
                                int mapAnchorNodeId = tarRelNode.anchorNodeList[0];

                                //if (targetGraph.m_nodes[mapActNodeId].isAligned || targetGraph.m_nodes[mapActNodeId].isAligned) continue;


                                // ActNode Map
                                if (!targetGraph.m_nodes[mapActNodeId].isAligned) alignScore += MapNode(actNodeId, mapActNodeId, targetGraph);

                                // Anchor Node
                                if (!targetGraph.m_nodes[mapAnchorNodeId].isAligned) alignScore += MapNode(anchorNodeId, mapAnchorNodeId, targetGraph);

                                // relation Node
                                alignScore += MapNode(relNode.nodeId, tarRelNode.nodeId, targetGraph);

                                break;
                            }
                        }
                    }
                }
            }

            // group_relation
            for (int qNi = 0; qNi < m_nodeNum; qNi++)
            {
                SemNode relNode = m_nodes[qNi];

                if (relNode.nodeType == "group_relation")
                {
                    if (relNode.anchorNodeList.Count == 0) continue;

                    int refNodeId = relNode.anchorNodeList[0];

                    // if this refNode have no canditate, then abandon this relation
                    if (!m_canditate_AlignNode.ContainsKey(refNodeId)) continue;

                    SemNode RefNode = this.m_nodes[refNodeId];

                    for (int tarNi = 0; tarNi < targetGraph.m_nodeNum; tarNi++)
                    {
                        SemNode tarRelNode = targetGraph.m_nodes[tarNi];
                        // skip the aligned nodes
                        if (relNode.matchingStatus == (int)SemNode.NodeMatchingStatus.ExplicitNode
                            && !tarRelNode.isAligned && tarRelNode.nodeType == "group_relation" && relNode.nodeName == tarRelNode.nodeName)
                        {
                            int tarRefNodeId = tarRelNode.anchorNodeList[0];

                            if (RefNode.isAligned)
                            {
                                if (m_nodeAlignMap[refNodeId] != tarRefNodeId) continue;
                            }
                            else
                            {
                                if (m_canditate_AlignNode[refNodeId].Contains(tarRefNodeId))
                                {
                                    alignScore += MapNode(refNodeId, tarRefNodeId, targetGraph);
                                }
                                else continue;
                            }

                            alignScore += MapNode(relNode.nodeId, tarRelNode.nodeId, targetGraph);

                            // set higher score for group node with more active objects
                            if (tarRelNode.activeNodeList.Count >= 5)
                            {
                                alignScore += 30;
                            }
                            else
                            {
                                alignScore += 10;
                            }

                            break;
                        }
                    }
                }
            }

            // every canditate List which have no alighed be alighed here
            foreach (var pa in m_canditate_AlignNode)
            {
                if (!m_nodes[pa.Key].isAligned)
                {
                    foreach (var v in pa.Value)
                    {
                        if (!targetGraph.m_nodes[v].isAligned)
                        {
                            alignScore += MapNode(pa.Key, v, targetGraph);
                            break;
                        }
                    }
                }
            }
        }

        public void mergeWithGraph(SemanticGraph inputGraph)
        {
            // insert all unaligned nodes
            for (int mi = 0; mi < inputGraph.m_nodeNum; mi++)
            {
                SemNode inputSgNode = inputGraph.m_nodes[mi];
                if (!inputSgNode.isAligned)
                {
                    // update graph
                    this.addNode(inputSgNode);
                    inputGraph.m_nodeAlignMap[mi] = this.m_nodeNum - 1;  // node id is the last node's id; save inserted node map
                }
            }

            // insert edges from matched ssg if it is not existing in current ssg
            for (int mei = 0; mei < inputGraph.m_edgeNum; mei++)
            {
                SemEdge inputSgEdge = inputGraph.m_edges[mei];

                int s = inputGraph.m_nodeAlignMap[inputSgEdge.sourceNodeId];
                int t = inputGraph.m_nodeAlignMap[inputSgEdge.targetNodeId];
                if (!this.isEdgeExist(s, t))
                {
                    this.addEdge(s, t);
                }
            }

            // update node labels, active and anchor node list
            this.parseNodeNeighbors();
        }


        public SemanticGraph alignAndMergeWithGraph(SemanticGraph sg)
        {
            double alignScore = 0;
            sg.alignObjectNodesToGraph(this, ref alignScore, true);
            sg.alignRelationNodesToGraph(this, ref alignScore);

            //add not aligned node
            this.mergeWithGraph(sg);

            return this;
        }

        public void setNodesUnAligned()
        {
            for (int i = 0; i < m_nodeNum; i++)
            {
                m_nodes[i].isAligned = false;
            }
        }

        public void reset()
        {
            setNodesUnAligned();
            m_canditate_AlignNode.Clear();
            m_nodeAlignMap.Clear();
        }

        public bool isAnchor(int nodeId)
        {
            SemNode currNode = m_nodes[nodeId];

            if (currNode.inEdgeNodeList.Count != 0)
            {
                for (int r = 0; r < currNode.inEdgeNodeList.Count; r++)
                {
                    int relNodeId = currNode.inEdgeNodeList[r];
                    SemNode relNode = m_nodes[relNodeId];

                    // if exist a relation node that is not an attribute node, then it is an anchor
                    if (relNode.nodeType != SSGNodeType[(int)SemNode.NodeType.Attri])
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        public string printGraph()
        {
            StringBuilder sb = new StringBuilder();
            using (System.IO.StringWriter ofs = new System.IO.StringWriter(sb))
            {
                ofs.Write("nodeNum " + m_nodeNum + "\n");
                for (int i = 0; i < m_nodeNum; i++)
                {
                    ofs.Write(i + "," + m_nodes[i].nodeType + "," + m_nodes[i].nodeName + ","
                        + Util.GetIntString(m_nodes[i].inEdgeNodeList, " ") + ","
                        + Util.GetIntString(m_nodes[i].outEdgeNodeList, " ") + "\n");
                }

                // save edges in format: edgeId,startId endId
                ofs.Write("edgeNum " + m_edgeNum + "\n");
                for (int i = 0; i < m_edgeNum; i++)
                {
                    ofs.Write(i + "," + m_edges[i].sourceNodeId + "," + m_edges[i].targetNodeId + "\n");
                }
            }
            return sb.ToString();
        }

        public void printGraph(string file_pth)
        {
            using (StreamWriter ofs = new StreamWriter(file_pth))
            {
                ofs.Write("nodeNum " + m_nodeNum + "\n");
                for (int i = 0; i < m_nodeNum; i++)
                {
                    ofs.Write(i + "," + m_nodes[i].nodeType + "," + m_nodes[i].nodeName + ","
                        + Util.GetIntString(m_nodes[i].inEdgeNodeList, " ") + ","
                        + Util.GetIntString(m_nodes[i].outEdgeNodeList, " ") + "\n");
                }

                // save edges in format: edgeId,startId endId
                ofs.Write("edgeNum " + m_edgeNum + "\n");
                for (int i = 0; i < m_edgeNum; i++)
                {
                    ofs.Write(i + "," + m_edges[i].sourceNodeId + "," + m_edges[i].targetNodeId + "\n");
                }
            }
        }
    }
}
